-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Embedding for String
s
#244
base: formal-verification
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! There are a few things where I feel like more clarity would help, and I wonder whether we could have fewer special cases.
val callableType = callableType ?: error("Signature not specified to buildExpEmbedding.") | ||
val argumentTypes = callableType.formalArgTypes | ||
val returnType = callableType.returnType | ||
check(argumentTypes.size == 1 || argumentTypes.size == 2) { "Operator should take exactly two arguments." } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: error doesn't match with condition.
...formver.core/src/org/jetbrains/kotlin/formver/embeddings/expression/OperatorExpEmbeddings.kt
Show resolved
Hide resolved
} | ||
|
||
class StringEmbeddingDetails(type: ClassTypeEmbedding) : ClassEmbeddingDetails(type, false) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: imho starting a class with an empty line like this looks sloppy, does the autoformatter not remove it?
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue | ||
|
||
class ClassEmbeddingDetails(val type: ClassTypeEmbedding, val isInterface: Boolean) : TypeInvariantHolder { | ||
open class ClassEmbeddingDetails(val type: ClassTypeEmbedding, val isInterface: Boolean) : TypeInvariantHolder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure that inheritance is a good idea here. While we are in a situation now where the special cases are all dijsoint, perhaps they could be given some other way, like via constructor parameters or using a method call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're redefining an implementation of one of the methods. It seems kind of like inheritance...
We could do sealed base and default / string implementation. Anyway, I don't like how I inherit from the extension function of some builder...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the key point is that you aren't really specialising the whole thing, so using composition (with something that might use inheritance itself) would feel more natural/clean to me.
@@ -41,6 +43,16 @@ internal class ClassPredicateBuilder private constructor(private val details: Cl | |||
body.addAll(builder.toAssertionsList()) | |||
} | |||
|
|||
fun forFieldNamed(name: String, action: FieldAssertionsBuilder.() -> Unit) { | |||
when (val field = details.fields[SimpleKotlinName(Name.identifier(name))]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be an error if this is used with a non-user field embedding?
else RuntimeTypeDomain.classTypeFunc(name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the string class need this amount of special treatment? I feel like it's a class like any other when it comes to RTTI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could do the same with String
not being special here.
We could do the same with Boolean
s, Int
s and Char
s as well.
It may have some sense to reduce the amount of code we need to add a new primitive type.
On the other hand, it feels nice when these type embeddings have short beautiful names.
My thought regarding String
was that it is treated somewhat special in Kotlin (having String
literals etc.) and that is the main reason I decided to do it like this
/** | ||
* Functions that should sometimes be handled separately by our conversion depending on arguments they're called with. | ||
*/ | ||
interface PartiallySpecialKotlinFunction : SeparatelyHandledKotlinFunction { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand the way this class is meant to be used. Do I understand correctly that baseEmbedding
is the function that is called if no special case is triggered, and tryInsertCall
is what decides whether the special behaviour should be used or not? Is this all being introduced only for the sake of StringPlusAnyFunction
, or are there other cases?
import org.jetbrains.kotlin.formver.viper.MangledName | ||
|
||
|
||
interface SeparatelyHandledKotlinFunction : FunctionEmbedding { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on what you mean by SeparatelyHandled
? How is it different from Special
?
import org.jetbrains.kotlin.formver.linearization.LinearizationContext | ||
import org.jetbrains.kotlin.formver.viper.ast.Exp | ||
|
||
interface InjectionBasedExpEmbedding : DirectResultExpEmbedding { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on the sense in which it is InjectionBased
? How should people understand the members?
builtinsOperation(inner.toViperBuiltinType(ctx), pos = ctx.source.asPosition, info = sourceRole.asInfo) | ||
} | ||
|
||
class OperatorExpEmbeddingTemplate(val type: TypeEmbedding, val refsOperation: InjectionImageFunction) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we pick an operator, it seems like we should already know whether it's binary or unary. Why allow for both types of calls on this type, instead of having two types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but please wait for Ilya's feedback.
val byName: Map<MangledName, FunctionEmbedding> | ||
get() = buildList { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this a function instead of a property with a getter.
@@ -49,7 +41,10 @@ import org.jetbrains.kotlin.utils.addToStdlib.ifTrue | |||
*/ | |||
class ProgramConverter(val session: FirSession, override val config: PluginConfiguration, override val errorCollector: ErrorCollector) : | |||
ProgramConversionContext { | |||
private val methods: MutableMap<MangledName, FunctionEmbedding> = SpecialKotlinFunctions.byName.toMutableMap() | |||
private val methods: MutableMap<MangledName, FunctionEmbedding> = | |||
SpecialKotlinFunctions.byName.toMutableMap().also { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not super important, but it's a bit weird that we are making a map, then turning it into a mutable map, and then mutating it again. If this is what we'll do with SpecialKotlinFunctions.byName
anyway, maybe let's have it take a mutable map/mutable map builder? That would also make it clear it is stateless.
when { | ||
existing is PartiallySpecialKotlinFunction -> | ||
existing.also { it.initBaseEmbedding(userFunction) } | ||
else -> userFunction.also { methods[symbol.embedName(this)] = it } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it's a bit strange to have when
here when you use if
above for almost the same check.
val signature = embedFullSignature(symbol) | ||
val callable = processCallable(symbol, signature) | ||
UserFunctionEmbedding(callable) | ||
override fun embedFunction(symbol: FirFunctionSymbol<*>): FunctionEmbedding { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code here seems to be the reason to have PartiallySpecialKotlinFunction
. Let us imagine we decided to only use SpecialKotlinFunction
, that would sometimes request an embedding. What piping are we missing for that? I assume ProgramConversionContext
would have to be passed somewhere where it currently isn't.
|
||
fun SpecialKotlinFunction.embedName(): ScopedKotlinName = callableId.embedFunctionName(callableType) | ||
|
||
object SpecialKotlinFunctions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment that explains that here we provide ExpEmbedding
implementations of built-in Kotlin functions.
name: String, | ||
body: (List<ExpEmbedding>, StmtConversionContext) -> ExpEmbedding, | ||
) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: unnecessary newline
body: (List<ExpEmbedding>, StmtConversionContext) -> ExpEmbedding, | ||
) { | ||
|
||
val newFunction = object : FullySpecialKotlinFunction { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, should FullySpecialKotlinFunction
not have an implementation which this thing passes all the data to, rather than creating a new object for each case? I'm not sure what the trade-offs are. (Maybe FullySpecialKotlinFunction
should just be a class?)
} | ||
|
||
data object NullLit : PureExpEmbedding { | ||
data object NullLit : LiteralEmbedding { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: unnecessary newline
sealed class ClassPredicateEnhancer { | ||
internal abstract fun applyAdditionalAssertions(builder: ClassPredicateBuilder) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not an interface?
This pull request introduces embeddings for Kotlin types
Char
andString
.Char
s are translated to Viper integers, whereasString
s are represented by viper sequences.Note that String is a sequence of viper
Int
s rather thanRef
s as this approach should be more lightweight.Operations implemented:
Apart from that, this PR
ExpEmbedding
s only sometimes (depending on the arguments passed)ExpEmbedding
sOverall, to add some operator on the primitive types it generally needs to be registered twice from now on (rather than thrice or more) and with some quite simple builders